/*=============================================================================

	ReShade 4 effect file
    github.com/martymcmodding

    Copyright (c) Pascal Gilcher. All rights reserved.

	Support me:
   		paypal.me/mcflypg
   		patreon.com/mcflypg

    ReGrade ALPHA 0.3

    changelog:

    0.1:    - initial release
    0.2:    - ported to refactored qUINT structure
            - removed RGB split from tone curve and lift gamma gain, replaced with
              with RGB selector
            - fixed bug with color remapper red affecting pure white
            - add more grading controls
            - remade lift gamma gain to fit convention
            - replaced histogram with McFly's '21 bomb ass histogram (TM)
            - added split toning
            - switched to internal LUT processing
    0.3:    - switched to LUT atlas for free arrangement of CC operations
            - added clipping mask
            - fixed bug with levels
            - closed UI sections by default
            - added vignette
            - improve color remap - still allows all hues, but now allows raising saturation
    0.4:    - change LUT sampling to tetrahedral
            - flatten LUTs, improves performance greatly
    0.5:    - add color balance node
            - add dark wash feature
            - extend levels to in/out
            - add gamma to adjustments

    * Unauthorized copying of this file, via any medium is strictly prohibited
 	* Proprietary and confidential

=============================================================================*/

/*
    TODO: modify functions to not depend on globals
*/

/*=============================================================================
	Preprocessor settings
=============================================================================*/

#ifndef REGRADE_ENABLE_HISTOGRAM
 #define REGRADE_ENABLE_HISTOGRAM		 0
#endif

/*=============================================================================
	UI Uniforms
=============================================================================*/

#define SECTION_NONE            "None"
#define SECTION_LEVELS          "Levels"
#define SECTION_ADJ             "Adjustments"
#define SECTION_LGG             "Lift Gamma Gain"
#define SECTION_WB              "White Balance"
#define SECTION_REMAP           "Color Remapping"
#define SECTION_TONECURVE       "Tone Curve"
#define SECTION_SPLIT           "Split Toning"
#define SECTION_CB              "Color Balance"

#define GRADE_ID_NONE            0
#define GRADE_ID_LEVELS_INPUT    1
#define GRADE_ID_ADJ             2
#define GRADE_ID_LGG             3
#define GRADE_ID_WB              4
#define GRADE_ID_REMAP           5
#define GRADE_ID_TONECURVE       6
#define GRADE_ID_SPLIT           7
#define GRADE_ID_CB              8

#define NUM_SECTIONS 8 //cannot use this to generate the nodes below! Increment and extend concat, add node

#define concat_all(a,b,c,d,e,f,g,h,i) a ## "\0" ## b ## "\0" ##c ## "\0" ##d ## "\0" ##e ## "\0" ##f ## "\0" ##g ## "\0" ##h ## "\0" ##i ## "\0"
#define CURR_ITEMS concat_all(SECTION_NONE, SECTION_LEVELS, SECTION_ADJ, SECTION_LGG, SECTION_WB, SECTION_REMAP, SECTION_TONECURVE, SECTION_SPLIT, SECTION_CB)

uniform int NODE1 <
	ui_type = "combo";
    ui_label = "Slot 1";
	ui_items = CURR_ITEMS;
    ui_category = "Order of Operations - duplicates do not have separate controls!";
> = 0;

uniform int NODE2 <
	ui_type = "combo";
    ui_label = "Slot 2";
	ui_items = CURR_ITEMS;
    ui_category = "Order of Operations - duplicates do not have separate controls!";
> = 0;

uniform int NODE3 <
	ui_type = "combo";
    ui_label = "Slot 3";
	ui_items = CURR_ITEMS;
    ui_category = "Order of Operations - duplicates do not have separate controls!";
> = 0;

uniform int NODE4 <
	ui_type = "combo";
    ui_label = "Slot 4";
	ui_items = CURR_ITEMS;
    ui_category = "Order of Operations - duplicates do not have separate controls!";
> = 0;

uniform int NODE5 <
	ui_type = "combo";
    ui_label = "Slot 5";
	ui_items = CURR_ITEMS;
    ui_category = "Order of Operations - duplicates do not have separate controls!";
> = 0;

uniform int NODE6 <
	ui_type = "combo";
    ui_label = "Slot 6";
	ui_items = CURR_ITEMS;
    ui_category = "Order of Operations - duplicates do not have separate controls!";
> = 0;

uniform int NODE7 <
	ui_type = "combo";
    ui_label = "Slot 7";
	ui_items = CURR_ITEMS;
    ui_category = "Order of Operations - duplicates do not have separate controls!";
> = 0;

uniform int NODE8 <
	ui_type = "combo";
    ui_label = "Slot 8";
	ui_items = CURR_ITEMS;
    ui_category = "Order of Operations - duplicates do not have separate controls!";
> = 0;

uniform float INPUT_BLACK_LVL <
	ui_type = "drag";
	ui_min = 0.0; ui_max = 255.0;
	ui_step = 1.0;
	ui_label = "Black Level In";
    ui_category = SECTION_LEVELS;
    ui_category_closed = true;
> = 0.0;

uniform float INPUT_WHITE_LVL <
	ui_type = "drag";
	ui_min = 0.0; ui_max = 255.0;
	ui_step = 1.0;
	ui_label = "White Level In";
    ui_category = SECTION_LEVELS;
    ui_category_closed = true;
> = 255.0;

uniform float OUTPUT_BLACK_LVL <
	ui_type = "drag";
	ui_min = 0.0; ui_max = 255.0;
	ui_step = 1.0;
	ui_label = "Black Level Out";
    ui_category = SECTION_LEVELS;
    ui_category_closed = true;
> = 0.0;

uniform float OUTPUT_WHITE_LVL <
	ui_type = "drag";
	ui_min = 0.0; ui_max = 255.0;
	ui_step = 1.0;
	ui_label = "White Level Out";
    ui_category = SECTION_LEVELS;
    ui_category_closed = true;
> = 255.0;

uniform float GRADE_CONTRAST <
	ui_type = "drag";
	ui_min = -1.0; ui_max = 1.0;
	ui_label = "Contrast";
    ui_category = SECTION_ADJ;
    ui_category_closed = true;
> = 0.0;

uniform float GRADE_EXPOSURE <
	ui_type = "drag";
	ui_min = -4.0; ui_max = 4.0;
	ui_label = "Exposure";
    ui_category = SECTION_ADJ;
    ui_category_closed = true;
> = 0.0;

uniform float GRADE_GAMMA <
	ui_type = "drag";
	ui_min = -1.0; ui_max = 1.0;
	ui_label = "Gamma";
    ui_category = SECTION_ADJ;
    ui_category_closed = true;
> = 0.0;

uniform float GRADE_SATURATION <
	ui_type = "drag";
	ui_min = -1.0; ui_max = 1.0;
	ui_label = "Saturation";
    ui_category = SECTION_ADJ;
    ui_category_closed = true;
> = 0.0;

uniform float GRADE_VIBRANCE <
	ui_type = "drag";
	ui_min = -1.0; ui_max = 1.0;
	ui_label = "Vibrance";
    ui_category = SECTION_ADJ;
    ui_category_closed = true;
> = 0.0;

uniform float3 INPUT_LIFT_COLOR <
  	ui_type = "color";
  	ui_label="Lift";
  	ui_category = SECTION_LGG;
      ui_category_closed = true;    
> = float3(0.5, 0.5, 0.5);

uniform float3 INPUT_GAMMA_COLOR <
  	ui_type = "color";
  	ui_label="Gamma";
  	ui_category = SECTION_LGG;
      ui_category_closed = true;    
> = float3(0.5, 0.5, 0.5);

uniform float3 INPUT_GAIN_COLOR <
  	ui_type = "color";
  	ui_label="Gain";
  	ui_category = SECTION_LGG;
      ui_category_closed = true;    
> = float3(0.5, 0.5, 0.5);

uniform float INPUT_COLOR_TEMPERATURE <
	ui_type = "drag";
	ui_min = 1700.0; ui_max = 40000.0;
    ui_step = 10.0;
	ui_label = "Color Temperature";
    ui_category = SECTION_WB;
    ui_category_closed = true;
> = 6500.0;

uniform float3 COLOR_REMAP_RED <
  	ui_type = "drag";
    ui_min = -1.0;
    ui_max = 1.0;
  	ui_label="Hue | Saturation | Value   : Red";
  	ui_category = SECTION_REMAP;
      ui_category_closed = true;   
> = float3(0.0, 0.0, 0.0);

uniform float3 COLOR_REMAP_ORANGE <
  	ui_type = "drag";
    ui_min = -1.0;
    ui_max = 1.0;
  	ui_label="Hue | Saturation | Value   : Orange";
  	ui_category = SECTION_REMAP;
      ui_category_closed = true;    
> = float3(0.0, 0.0, 0.0);

uniform float3 COLOR_REMAP_YELLOW <
  	ui_type = "drag";
    ui_min = -1.0;
    ui_max = 1.0;
  	ui_label="Hue | Saturation | Value   : Yellow";
  	ui_category = SECTION_REMAP;
      ui_category_closed = true;     
> = float3(0.0, 0.0, 0.0);

uniform float3 COLOR_REMAP_GREEN <
  	ui_type = "drag";
    ui_min = -1.0;
    ui_max = 1.0;
  	ui_label="Hue | Saturation | Value   : Green";
  	ui_category = SECTION_REMAP;
      ui_category_closed = true;   
> = float3(0.0, 0.0, 0.0);

uniform float3 COLOR_REMAP_AQUA <
  	ui_type = "drag";
    ui_min = -1.0;
    ui_max = 1.0;
  	ui_label="Hue | Saturation | Value   : Aqua";
	ui_category = SECTION_REMAP;
    ui_category_closed = true;
> = float3(0.0, 0.0, 0.0);

uniform float3 COLOR_REMAP_BLUE <
  	ui_type = "drag";
    ui_min = -1.0;
    ui_max = 1.0;
  	ui_label="Hue | Saturation | Value   : Blue";
  	ui_category = SECTION_REMAP;
      ui_category_closed = true;    
> = float3(0.0, 0.0, 0.0);

uniform float3 COLOR_REMAP_MAGENTA <
  	ui_type = "drag";
    ui_min = -1.0;
    ui_max = 1.0;
  	ui_label="Hue | Saturation | Value   : Magenta";
  	ui_category = SECTION_REMAP;
      ui_category_closed = true;     
> = float3(0.0, 0.0, 0.0);

uniform float TONECURVE_SHADOWS <
	ui_type = "drag";
	ui_min = -1.00; ui_max = 1.00;
	ui_label = "Shadows";
    ui_category = SECTION_TONECURVE;
    ui_category_closed = true;
> = 0.00;

uniform float TONECURVE_DARKS <
	ui_type = "drag";
	ui_min = -1.00; ui_max = 1.00;
	ui_label = "Darks";
    ui_category = SECTION_TONECURVE;
    ui_category_closed = true;
> = 0.00;

uniform float TONECURVE_LIGHTS <
	ui_type = "drag";
	ui_min = -1.00; ui_max = 1.00;
	ui_label = "Lights";
    ui_category = SECTION_TONECURVE;
    ui_category_closed = true;
> = 0.00;

uniform float TONECURVE_HIGHLIGHTS <
	ui_type = "drag";
	ui_min = -1.00; ui_max = 1.00;
	ui_label = "Highlights";
    ui_category = SECTION_TONECURVE;
    ui_category_closed = true;
> = 0.00;

uniform float TONECURVE_DARKWASH_RANGE <
	ui_type = "drag";
	ui_min = 0.0; ui_max = 1.0;
	ui_label = "Dark Wash Range";
    ui_category = SECTION_TONECURVE;
    ui_category_closed = true;
> = 0.2;
uniform float TONECURVE_DARKWASH_INT <
	ui_type = "drag";
	ui_min = 0.0; ui_max = 1.0;
	ui_label = "Dark Wash Intensity";
    ui_category = SECTION_TONECURVE;
    ui_category_closed = true;
> = 0.0;

uniform float3 SPLITTONE_SHADOWS <
  	ui_type = "color";
  	ui_label="Shadow Tint";
  	ui_category = SECTION_SPLIT;
    ui_category_closed = true;    
> = float3(1.0, 1.0, 1.0);

uniform float3 SPLITTONE_HIGHLIGHTS <
  	ui_type = "color";
  	ui_label="Highlight Tint";
  	ui_category = SECTION_SPLIT;
    ui_category_closed = true;    
> = float3(1.0, 1.0, 1.0);

uniform float SPLITTONE_BALANCE <
	ui_type = "drag";
	ui_min = 0.00; ui_max = 1.00;
	ui_label = "Balance";
    ui_category = SECTION_SPLIT;
    ui_category_closed = true;
> = 0.5;

uniform float3 CB_BALANCE_CR <
	ui_type = "drag";
	ui_min = -1.0; ui_max = 1.0;
	ui_label = "Cyan<->Red         Shadows|Midtones|Highlights";
    ui_category = SECTION_CB;
    ui_category_closed = true;
> = float3(0.0,0.0,0.0);

uniform float3 CB_BALANCE_BL <
	ui_type = "drag";
	ui_min = -1.0; ui_max = 1.0;
	ui_label = "Blue<->Yellow      Shadows|Midtones|Highlights";
    ui_category = SECTION_CB;
    ui_category_closed = true;
> = float3(0.0,0.0,0.0);

uniform float3 CB_BALANCE_GM <
	ui_type = "drag";
	ui_min = -1.0; ui_max = 1.0;
	ui_label = "Green<->Magenta    Shadows|Midtones|Highlights";
    ui_category = SECTION_CB;
    ui_category_closed = true;
> = float3(0.0,0.0,0.0);

uniform int HISTOGRAM_MODE <
	ui_type = "combo";
    ui_label = "Histogram Mode";
	ui_items = "Off\0Luminance\0RGB\0RGB + Luminance (separated)\0";
    ui_category = "Statistics";
> = 1;

uniform bool HISTOGRAM_UIENABLE <
    ui_label = "Hide Histogram when ReShade menu is closed";
    ui_category = "Statistics";
> = true;

uniform bool SHOW_CLIPPING <
    ui_label = "Show Clipping";
    ui_category = "Statistics";
> = false;

/*
uniform float4 tempF1 <
    ui_type = "drag";
    ui_min = -100.0;
    ui_max = 100.0;
> = float4(1,1,1,1);

uniform float4 tempF2 <
    ui_type = "drag";
    ui_min = -100.0;
    ui_max = 100.0;
> = float4(1,1,1,1);

uniform float4 tempF3 <
    ui_type = "drag";
    ui_min = -100.0;
    ui_max = 100.0;
> = float4(1,1,1,1);
*/
/*=============================================================================
	Textures, Samplers, Globals
=============================================================================*/

uniform float FRAMETIME < source = "frametime";  >;
uniform uint FRAMECOUNT  < source = "framecount"; >;
uniform bool OVERLAY_OPEN < source = "overlay_open"; >;

//2^n
#define GRID_SCALE		0
#define HISTORY_SCALE	3

#define THREAD_CONFLICT_RES_SIZE 64
#define INTERNAL_LUT_SIZE        32

//can't touch this dun dundundun can't touch this
#define GRID_DIM_X				(BUFFER_WIDTH >> GRID_SCALE)
#define GRID_DIM_Y				(BUFFER_HEIGHT >> GRID_SCALE)
#define SUB_GRID_DIM_X  		(GRID_DIM_X >> HISTORY_SCALE)
#define SUB_GRID_DIM_Y  		(GRID_DIM_Y >> HISTORY_SCALE)
#define HISTORY_SIZE			((1 << HISTORY_SCALE) * (1 << HISTORY_SCALE))

texture ColorInputTex : COLOR;
sampler ColorInput 	{ Texture = ColorInputTex; MinFilter=POINT; MipFilter=POINT; MagFilter=POINT;};

//need to include Global here, as on DX9, Vertex Shaders can only access the first 4 declared samplers
//and the following textures occupy all 4 slots, but ColorInput  must be readable
#include "qUINT\Global.fxh"

texture2D HistogramTexRGBIRaw		    { Width = 256;   Height = THREAD_CONFLICT_RES_SIZE;               Format = RGBA32F;  	};
texture2D HistogramTexRGBI			    { Width = 256;   Height = 1;                Format = RGBA32F;  	};
texture2D HistogramHistoryTexRGBI	    { Width = 256;   Height = HISTORY_SIZE;     Format = RGBA32F;  	};
texture2D LUTAtlas	                    { Width = INTERNAL_LUT_SIZE * INTERNAL_LUT_SIZE;   Height = INTERNAL_LUT_SIZE * NUM_SECTIONS;     Format = RGBA16F;  	};
texture2D LUTFlattened	                { Width = INTERNAL_LUT_SIZE * INTERNAL_LUT_SIZE;   Height = INTERNAL_LUT_SIZE;     Format = RGB10A2;  	};

sampler2D sHistogramTexRGBIRaw			{ Texture = HistogramTexRGBIRaw;  };
sampler2D sHistogramTexRGBI			    { Texture = HistogramTexRGBI;  };
sampler2D sHistogramHistoryTexRGBI 	    { Texture = HistogramHistoryTexRGBI; };
sampler2D sLUTAtlas		                { Texture = LUTAtlas;  };
sampler2D sLUTFlattened		            { Texture = LUTFlattened;  };

/*=============================================================================
	Includes
=============================================================================*/


#include "qUINT\Colorspaces.fxh"
#include "qUINT\Whitebalance.fxh"

/*=============================================================================
	Vertex Shader
=============================================================================*/

struct VSOUT
{
	float4                  vpos        : SV_Position;
    float2                  uv          : TEXCOORD0;    
};

struct VSOUTRGBI
{
	float4                  vpos        : SV_Position;
    float2                  uv          : TEXCOORD0;
    nointerpolation uint 	channel		: TEXCOORD1; 
};

//writes single histogram for current frame
VSOUTRGBI VS_HistogramGenRGBIRaw(in uint vertex_id : SV_VertexID)
{
    VSOUTRGBI o;

    o.channel = vertex_id % 4u;          //channel currently processed
    vertex_id /= 4u;                     //reuse same math as single channel histogram

    uint rowtowrite = vertex_id % THREAD_CONFLICT_RES_SIZE;   //write points into different rows to improve performance (less threads write to same texel at the same time)   

    [flatten]
    if(HISTOGRAM_MODE == 0 //off
    || HISTOGRAM_MODE == 1 && o.channel != 3 //luma only
    || (HISTOGRAM_UIENABLE && !OVERLAY_OPEN)
    ) rowtowrite = -10000; //shift out of viewport -> pixel is not drawn

    static const uint grid_subgrid_ratio = 1u << HISTORY_SCALE; //grid = total grid that is analyzed, subgrid = part of the grid that is analyzed this frame
    uint history_num = FRAMECOUNT % HISTORY_SIZE;

    uint2 current_subgrid_point = uint2(vertex_id % SUB_GRID_DIM_X, vertex_id / SUB_GRID_DIM_X);
    uint2 current_subgrid_to_grid_offset = uint2(history_num % grid_subgrid_ratio, history_num / grid_subgrid_ratio);
    uint2 current_grid_point = current_subgrid_point * grid_subgrid_ratio + current_subgrid_to_grid_offset;

    //transform to final fullscreen pixel grid to sample
    static const uint grid_to_pixelraster_ratio = 1u << GRID_SCALE;
    uint2 current_pixel_point = current_grid_point * grid_to_pixelraster_ratio; //upscale grid to fullres texel coord
    current_pixel_point += grid_to_pixelraster_ratio / 2; //center grid

    float4 c = tex2Dfetch(ColorInput, current_pixel_point);
    c.w = dot(c.rgb, float3(0.299, 0.587, 0.114));

    float val = dot(c, int4(0, 1, 2, 3) == o.channel.xxxx); //select channel of sample to add to bin
    //write point for 256x1 texture
    o.uv.x = (round(val * 255) + 0.5) / 256.0;
   	o.uv.y = (0.5 + rowtowrite) / THREAD_CONFLICT_RES_SIZE; //select row to write to
   	o.vpos = float4(o.uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); //can use the borked uv, as we're writing points anyways, otherwise define UV regularly and use transformed UV to define vpos
    return o;
}

VSOUT VS_HistogramHistory(in uint id : SV_VertexID)
{
    VSOUT o;    

   	uint history_size = HISTORY_SIZE; 
    uint history_num = FRAMECOUNT % history_size;

    //setup standard triangle for the UV at location
    o.uv.x = (id == 2) ? 2.0 : 0.0;
    o.uv.y = (id == 1) ? 2.0 : 0.0;

    //define area to write to in UV space of target
    float2 target_uv = o.uv;
    //transform to current row
    target_uv.y += history_num;
    target_uv.y /= history_size;
    o.vpos = float4(target_uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
    return o;
}

VSOUT VS_Basic(in uint id : SV_VertexID)
{
    VSOUT o;
    o.uv.x = (id == 2) ? 2.0 : 0.0;
    o.uv.y = (id == 1) ? 2.0 : 0.0;
    o.vpos = float4(o.uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
    return o;
}

/*=============================================================================
	Functions
=============================================================================*/

float3 color_remapper(in float3 rgb)
{
	float3 hsl = Colorspace::rgb_to_hsl(rgb);

	static const float hue_nodes[8] = {	 0.0, 1.0/12.0, 2.0/12.0, 4.0/12.0, 6.0/12.0, 8.0/12.0, 10.0/12.0, 1.0};

	float risingedges[7];
	for(int j = 0; j < 7; j++)
		risingedges[j] = linearstep(hue_nodes[j], hue_nodes[j+1], hsl.x);

	float3 remapped = 0;
    remapped += Colorspace::hsl_to_rgb(float3(frac(hue_nodes[0] + 0.5 * abs(COLOR_REMAP_RED.x) * COLOR_REMAP_RED.x),     COLOR_REMAP_RED.y + 1.0,     exp2(COLOR_REMAP_RED.z))     * float3(1.0, hsl.y, hsl.z)) * ((1.0 - risingedges[0]) + risingedges[6]); //red - yes, this needs a +
	remapped += Colorspace::hsl_to_rgb(float3(frac(hue_nodes[1] + 0.5 * abs(COLOR_REMAP_ORANGE.x) * COLOR_REMAP_ORANGE.x),  COLOR_REMAP_ORANGE.y + 1.0,  exp2(COLOR_REMAP_ORANGE.z))  * float3(1.0, hsl.y, hsl.z)) * ((1.0 - risingedges[1]) * risingedges[0]); //orange
	remapped += Colorspace::hsl_to_rgb(float3(frac(hue_nodes[2] + 0.5 * abs(COLOR_REMAP_YELLOW.x) * COLOR_REMAP_YELLOW.x),  COLOR_REMAP_YELLOW.y + 1.0,  exp2(COLOR_REMAP_YELLOW.z))  * float3(1.0, hsl.y, hsl.z)) * ((1.0 - risingedges[2]) * risingedges[1]); //yellow
	remapped += Colorspace::hsl_to_rgb(float3(frac(hue_nodes[3] + 0.5 * abs(COLOR_REMAP_GREEN.x) * COLOR_REMAP_GREEN.x),   COLOR_REMAP_GREEN.y + 1.0,   exp2(COLOR_REMAP_GREEN.z))   * float3(1.0, hsl.y, hsl.z)) * ((1.0 - risingedges[3]) * risingedges[2]); //green
	remapped += Colorspace::hsl_to_rgb(float3(frac(hue_nodes[4] + 0.5 * abs(COLOR_REMAP_AQUA.x) * COLOR_REMAP_AQUA.x),    COLOR_REMAP_AQUA.y + 1.0,    exp2(COLOR_REMAP_AQUA.z))    * float3(1.0, hsl.y, hsl.z)) * ((1.0 - risingedges[4]) * risingedges[3]); //aqua
	remapped += Colorspace::hsl_to_rgb(float3(frac(hue_nodes[5] + 0.5 * abs(COLOR_REMAP_BLUE.x) * COLOR_REMAP_BLUE.x),    COLOR_REMAP_BLUE.y + 1.0,    exp2(COLOR_REMAP_BLUE.z))    * float3(1.0, hsl.y, hsl.z)) * ((1.0 - risingedges[5]) * risingedges[4]); //blue
	remapped += Colorspace::hsl_to_rgb(float3(frac(hue_nodes[6] + 0.5 * abs(COLOR_REMAP_MAGENTA.x) * COLOR_REMAP_MAGENTA.x), COLOR_REMAP_MAGENTA.y + 1.0, exp2(COLOR_REMAP_MAGENTA.z)) * float3(1.0, hsl.y, hsl.z)) * ((1.0 - risingedges[6]) * risingedges[5]); //magenta
    //fix white
    remapped = lerp(rgb, remapped, smoothstep(0, 1.0/255, hsl.y));

	return remapped;
}

float tonecurve(float x, float p0, float p1, float p2, float p3)
{
	//adjust parameter range to be more user friendly
	p0 *= p0 > 0 ? 0.5 : 0.5;
	p1 *= p1 > 0 ? 1 : 2.0;
	p2 *= p2 > 0 ? 4 : 1.5;	
	p3 *= p3 > 0 ? 3 : 1.0;

	float x0 = smoothstep(0.3, 0.0, x); //shadows
    float x1 = smoothstep(0.6, 0.0, x); //darks
    float x2 = smoothstep(0.35, 1.0, x); //lights
    float x3 = smoothstep(0.7, 1.0, x); //highlights

	float4 pn = float4(p0, p1, p2, p3);
	float4 xn = float4(x0, x1, x2, x3);	

    //this could be vectorized, but I'd like to keep it separate if more get added

	x = pow(abs(x), exp2(dot(-pn, xn)));

    float t = x / (TONECURVE_DARKWASH_RANGE+1e-6);
    float dw = (10.0 * TONECURVE_DARKWASH_RANGE - x) * t * t * (1.0 / 31.25) + TONECURVE_DARKWASH_RANGE;
    x = lerp(x, dw, x > TONECURVE_DARKWASH_RANGE ? 0 : TONECURVE_DARKWASH_INT);


	return x;
}

float3 input_remap( float3 x,   
					 float3 blacklevel, 
					 float3 whitelevel,
                     float3 blacklevel_out,
                     float3 whitelevel_out)
{
	x = linearstep(blacklevel, whitelevel + 1e-6, x);   
    x = lerp(blacklevel_out, whitelevel_out, x); 
	return x;
}

float3 lgg(float3 x, float3 lift, float3 gamma, float3 gain)
{
    //https://en.wikipedia.org/wiki/ASC_CDL
    x = pow(saturate((x * gain) + lift), gamma);
    return x;
}

float3 srgb_to_linear(float3 S)
{
    return S < 0.04045 ? S / 12.92 : pow(((S + 0.055) / 1.055), 2.4);
}

float3 linear_to_srgb(float3 L)
{
    return L <= 0.0031308 ? L * 12.92 : 1.055 * pow(L, rcp(2.4)) - 0.055;
}

float extended_overlay(float base, float blend)
{
    float sharpness = 7; //max ~100 without precision loss
    float poly_tri = 1 - pow(pow(base, sharpness) + pow(1 - base, sharpness), rcp(sharpness)); //form smooth triangle (vs hard triangle in regular overlay)
    return base + poly_tri * (blend * 2 - 1);
}

float3 splittone(float3 c)
{
    float cluma = dot(c, float3(0.299, 0.587, 0.114));
    float2 a = saturate(float2(cluma, 1 - cluma));
    a *= a;
    a *= a;
    //a *= a;
    float bal = lerp(1 - a.y, a.x, SPLITTONE_BALANCE);

    float3 tint_hsl = Colorspace::rgb_to_hsl(lerp(SPLITTONE_SHADOWS, SPLITTONE_HIGHLIGHTS, bal));
    tint_hsl.z = 0.5;
    float3 tint_rgb = Colorspace::hsl_to_rgb(tint_hsl);

    c.x = extended_overlay(c.x, tint_rgb.x);
    c.y = extended_overlay(c.y, tint_rgb.y);
    c.z = extended_overlay(c.z, tint_rgb.z);
    return c;
}

float3 color_balance(float3 c)
{
    float3 c_hsl = Colorspace::rgb_to_hsl(c);

    float2 hue_cartesian; sincos(c_hsl.x * 6.283185307, hue_cartesian.y, hue_cartesian.x); hue_cartesian *= c_hsl.y;
    float3 splitw = float3(saturate(0.5 - c_hsl.z), 0.5, 0.5 - saturate(0.5 - c_hsl.z));
    float2 cyanred      = float2(1.0, 0.0) * dot(splitw, CB_BALANCE_CR);
    float2 blueyellow   = float2(0.5, 0.866025) * dot(splitw, CB_BALANCE_BL);
    float2 greenmagenta = float2(0.5, -0.866025) * dot(splitw, CB_BALANCE_GM);

    hue_cartesian += (cyanred + blueyellow + greenmagenta); 
    float newsat = saturate(length(hue_cartesian));

    float pi = 3.1415927;
    float newhue = frac(atan2(hue_cartesian.y, hue_cartesian.x) / 6.283185307);
    return Colorspace::hsl_to_rgb(float3(newhue, newsat, c_hsl.z));
}

float3 adjustments(float3 col, float exposure, float contrast, float gamma, float vibrance, float saturation)
{
    col = srgb_to_linear(col);
    col *= exp2(exposure); //exposure in linear space - this makes no _visual_ difference but alters the response of the exposure curve
    col = linear_to_srgb(col);
    col = saturate(col);
    col = pow(col, exp2(-gamma));
    float3 contrasted = col - 0.5;
    contrasted = (contrasted / (0.5 + abs(contrasted))) + 0.5; //CJ.dk
    col = lerp(col, contrasted, contrast);
    col = Colorspace::rgb_to_hsl(col);
    col.y = pow(abs(col.y), exp2(-vibrance)) * (saturation + 1.0);
    col = Colorspace::hsl_to_rgb(col);
    return col;
}

/*=============================================================================
	Pixel Shaders
=============================================================================*/

void PSWritePointRGBI(in VSOUTRGBI i, out float4 o : SV_Target0)
{
	o = float4(int4(0, 1, 2, 3) == i.channel.xxxx) * rcp(SUB_GRID_DIM_X * SUB_GRID_DIM_Y);
}

void PSHistogramWriteToHistoryRGBIRaw(in VSOUT i, out float4 o : SV_Target0)
{
    o = 0;
    for(int j = 0; j < THREAD_CONFLICT_RES_SIZE; j++)
        o += tex2Dlod(sHistogramTexRGBIRaw, float4(i.uv.x, (j + 0.5)/THREAD_CONFLICT_RES_SIZE,0,0));
}

void PSHistogramAverageHistoryRGBI(in VSOUT i, out float4 o : SV_Target0)
{
	o = 0;
    for(float j = 0; j < HISTORY_SIZE / 2; j++)
        o += tex2Dlod(sHistogramHistoryTexRGBI, float2(i.uv.x, (j * 2 + 1) / 64.0), 0);
	o /= HISTORY_SIZE;
}

void PSHistogramDisplayRGBI(in VSOUT i, out float4 o : SV_Target0)
{
    float4 histogram = tex2Dfetch(sHistogramTexRGBI, int2(i.vpos.x % 256, 0)).xyzw;
    histogram = log(histogram + 1) * 12;
    histogram = min(histogram, 0.3);

    float4 histogram_mask = histogram > (1.0 - i.uv.y);

    o = 0;
    switch(HISTOGRAM_MODE)
    {
        case 0:
            o = 0;
            break;
        case 1:
            o = i.vpos.x < 256 ? float4(histogram_mask.www, histogram_mask.w > 0) : 0;
            break;
        case 2:
            o = i.vpos.x < 256 ? float4(histogram_mask.rgb, any(histogram_mask.rgb > 0)) : 0;
            break;
        case 3:
            int channel = floor(i.vpos.x / 256);
            histogram_mask *= channel.xxxx == int4(0,1,2,3); //mask channel
            histogram_mask = channel == 3 ? histogram_mask.w : histogram_mask; //visualize alpha
            o = histogram_mask;
            o.w = any(histogram_mask > 0);
            break;       
    }
}

void PSReGradeMain(in VSOUT i, out float3 o : SV_Target0)
{
    int gradeop = uint(floor(i.vpos.y)) / uint(INTERNAL_LUT_SIZE) + 1; //0 is "skip"

    float2 lut_xy = floor(i.vpos.xy); lut_xy.y %= INTERNAL_LUT_SIZE;
    float3 col = float3(lut_xy.x % INTERNAL_LUT_SIZE, lut_xy.y, floor(lut_xy.x / INTERNAL_LUT_SIZE));
    col = saturate(col / (INTERNAL_LUT_SIZE - 1.0));

    //doing one of the ops per atlas index - cycling over them into a single LUT compiles far too slow, atlas is actually faster
    switch(gradeop)
    {
        case GRADE_ID_LEVELS_INPUT: col = input_remap(col, INPUT_BLACK_LVL / 255.0, INPUT_WHITE_LVL / 255.0, OUTPUT_BLACK_LVL / 255.0, OUTPUT_WHITE_LVL / 255.0); break;
        case GRADE_ID_ADJ:          col = adjustments(col, GRADE_EXPOSURE, GRADE_CONTRAST, GRADE_GAMMA, GRADE_VIBRANCE, GRADE_SATURATION); break;
        case GRADE_ID_LGG:          col = lgg(col, INPUT_LIFT_COLOR - 0.5, 1.0 - INPUT_GAMMA_COLOR + 0.5, INPUT_GAIN_COLOR + 0.5); break;
        case GRADE_ID_WB:           col = Whitebalance::set_white_balance(col, INPUT_COLOR_TEMPERATURE); break;
        case GRADE_ID_REMAP:        col = color_remapper(col); break;
        case GRADE_ID_TONECURVE:    [unroll]for(int c = 0; c < 3; c++) col[c] = tonecurve(col[c], TONECURVE_SHADOWS, TONECURVE_DARKS, TONECURVE_LIGHTS, TONECURVE_HIGHLIGHTS); break;
        case GRADE_ID_SPLIT:        col = splittone(col); break;
        case GRADE_ID_CB:           col = color_balance(col); break;        
    };

    o = saturate(col);
}

//I'm really proud of this solution, it's orders of magnitude simpler than the ACES reference
float3 tetrahedral_volume_sampling(sampler volumesampler, float3 pn, int volume_size, int atlas_idx)
{
    float3 p = saturate(pn) * (volume_size - 1);

    //lo and hi corners inside lattice cell
    float3 corner000 = floor(p); float3 corner111 = ceil(p);

    //relative position inside lattice cell
    float3 delta = p - corner000;

    //todo optimize?
    float3 maxaxis  = delta.x > delta.y && delta.x > delta.z ? float3(1,0,0) //x largest
                    : delta.y > delta.z ? float3(0,1,0) //y largest
                    : float3(0,0,1); //z largest
    float3 minaxis  = delta.x < delta.y && delta.x < delta.z ? float3(1,0,0) //x smallest
                    : delta.y < delta.z ? float3(0,1,0) //y smallest
                    : float3(0,0,1); //z smallest

    //3D coords of the 2 dynamic interpolants in the lattice    
    int3 cornermin = lerp(corner111, corner000, minaxis);
    int3 cornermax = lerp(corner000, corner111, maxaxis);
   
    //texture coords of the participating interpolants. LUT atlas extends vertically.
    int2 texturecoord000 = int2(corner000.x + corner000.z * volume_size, corner000.y + volume_size * atlas_idx);
    int2 texturecoordmax = int2(cornermax.x + cornermax.z * volume_size, cornermax.y + volume_size * atlas_idx);
    int2 texturecoordmin = int2(cornermin.x + cornermin.z * volume_size, cornermin.y + volume_size * atlas_idx);
    int2 texturecoord111 = int2(corner111.x + corner111.z * volume_size, corner111.y + volume_size * atlas_idx);

    float maxv = dot(maxaxis, delta);
    float minv = dot(minaxis, delta);
    float medv = dot(1 - maxaxis - minaxis, delta);

    float4 lattice_weights = float4(1, maxv, medv, minv);
    lattice_weights.xyz -= lattice_weights.yzw;
    
    return  tex2Dfetch(volumesampler, texturecoord000).rgb * lattice_weights.x            
          + tex2Dfetch(volumesampler, texturecoordmax).rgb * lattice_weights.y
          + tex2Dfetch(volumesampler, texturecoordmin).rgb * lattice_weights.z
          + tex2Dfetch(volumesampler, texturecoord111).rgb * lattice_weights.w;
}

void PSLUTFlatten(in VSOUT i, out float3 o : SV_Target0)
{
    const float2 lutsize = float2(INTERNAL_LUT_SIZE * INTERNAL_LUT_SIZE, INTERNAL_LUT_SIZE);
    const float2 invlutsize = rcp(lutsize);

    int op_sequence[NUM_SECTIONS] = {NODE1,NODE2,NODE3,NODE4,NODE5,NODE6,NODE7, NODE8};

    float2 lut_xy = floor(i.vpos.xy); lut_xy.y %= INTERNAL_LUT_SIZE;
    float3 col = float3(lut_xy.x % INTERNAL_LUT_SIZE, lut_xy.y, floor(lut_xy.x / INTERNAL_LUT_SIZE));
    col = saturate(col / (INTERNAL_LUT_SIZE - 1.0));

    for(int op = 0; op < NUM_SECTIONS; op++)
    {
        uint curr_op = op_sequence[op];
        if(curr_op == GRADE_ID_NONE) continue;
        col = tetrahedral_volume_sampling(sLUTAtlas, col, INTERNAL_LUT_SIZE, curr_op - 1);
    }
    o = col;
}

void PSLUTApplyFlattened(in VSOUT i, out float3 o : SV_Target0)
{
    const float2 lutsize = float2(INTERNAL_LUT_SIZE * INTERNAL_LUT_SIZE, INTERNAL_LUT_SIZE);
    const float2 invlutsize = rcp(lutsize);

    float3 col = tex2D(ColorInput, i.uv).rgb; 
    col = tetrahedral_volume_sampling(sLUTFlattened, col, INTERNAL_LUT_SIZE, 0); //no atlas, no LUT

    float l = dot(col, 0.3333333);
    col = SHOW_CLIPPING ? lerp(col,1-col,(l-l*l)<0.003):col;
    o = col;
}

/*=============================================================================
	Techniques
=============================================================================*/

technique qUINT_ReGrade
{
	pass
	{
		VertexShader = VS_Basic;
		PixelShader = PSReGradeMain;
        RenderTarget = LUTAtlas;
	}
    pass
	{
		VertexShader = VS_Basic;
		PixelShader = PSLUTFlatten;
        RenderTarget = LUTFlattened;
	}
    pass    
	{
		VertexShader = VS_Basic;
		PixelShader = PSLUTApplyFlattened;
	}
#if REGRADE_ENABLE_HISTOGRAM != 0   
    pass
	{
		VertexShader = VS_HistogramGenRGBIRaw;
		PixelShader = PSWritePointRGBI;
		RenderTarget = HistogramTexRGBIRaw;
		PrimitiveTopology = POINTLIST;
		VertexCount = SUB_GRID_DIM_X * SUB_GRID_DIM_Y * 4; //RGBI
		ClearRenderTargets = true; 
		BlendEnable = true; 
		SrcBlend = ONE; 
		DestBlend = ONE;
		SrcBlendAlpha = ONE;
		DestBlendAlpha = ONE;
    }
    pass
	{
		VertexShader = VS_HistogramHistory;
		PixelShader = PSHistogramWriteToHistoryRGBIRaw;
		RenderTarget = HistogramHistoryTexRGBI;
		ClearRenderTargets = false; //should be true by default but you never know...
									//set to true to see how each line is written
	}
    pass
	{
		VertexShader = VS_Basic;
		PixelShader = PSHistogramAverageHistoryRGBI;
		RenderTarget = HistogramTexRGBI;
	}
	pass
	{
		VertexShader = VS_Basic;
		PixelShader = PSHistogramDisplayRGBI;
        BlendEnable = true; 
		SrcBlend = SRCALPHA; 
		DestBlend = INVSRCALPHA;
	}
#endif
}
